package com.adee.java.jdbc.pooled;

import com.adee.java.jdbc.util.JdkProxyUtil;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.time.Duration;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

public class MyDatasourceByCommonsPool2 {
    private String url = "jdbc:mysql://8.142.104.99:3306/db1?characterEncoding=utf8";
    private String user = "root";
    private String password = "5UNRltoxlye6Zpjd";
    private String driverClass = "com.mysql.cj.jdbc.Driver";
    // private int initialPoolSize = 3;
    private int minPoolSize = 2;
    private int maxPoolSize = 5;
    // Connection连接池
    private BlockingQueue<Connection> pool;
    // 记录Connection总数
    private AtomicInteger remain;

    private ObjectPool<Connection> innerPool;

    public MyDatasourceByCommonsPool2() {
        init();
    }

    public static void main(String[] args) throws Exception {
        MyDatasourceByCommonsPool2 ds = new MyDatasourceByCommonsPool2();
        Connection conn1 = ds.getConnection();
        Connection conn2 = ds.getConnection();
        Connection conn3 = ds.getConnection();
        Connection conn4 = ds.getConnection();
        Connection conn5 = ds.getConnection();
        conn1.close();
        conn2.close();
        conn3.close();
        conn4.close();
        conn5.close();
        Connection conn6 = ds.getConnection();
        Connection conn7 = ds.getConnection();
    }

    private void init() {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream("MyDatasource.properties");
        Properties properties = new Properties();
        Field field = null;
        try {
            properties.load(is);
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driverClass = properties.getProperty("driverClass");
            // initialPoolSize = Integer.parseInt(properties.getProperty("initialPoolSize"));
            minPoolSize = Integer.parseInt(properties.getProperty("minPoolSize"));
            maxPoolSize = Integer.parseInt(properties.getProperty("maxPoolSize"));
            GenericObjectPoolConfig<Connection> config = new GenericObjectPoolConfig<>();
            config.setMaxTotal(maxPoolSize);
            config.setMinIdle(minPoolSize);
            config.setMaxIdle(minPoolSize);
            config.setTimeBetweenEvictionRuns(Duration.ofSeconds(10));
            ConnectionPooledObjectFactory factory = new ConnectionPooledObjectFactory(properties);
            innerPool = new GenericObjectPool<>(factory, config);
            Class clz = ConnectionPooledObjectFactory.class;
            field = clz.getDeclaredField("pool");
            field.setAccessible(true);
            field.set(factory, innerPool);
        } catch (IOException | NoSuchFieldException | IllegalAccessException e) {
            if(null != field) {
                field.setAccessible(false);
            }
            e.printStackTrace();
        }

    }

    public Connection getConnection() throws Exception {
        return innerPool.borrowObject();
    }
}

// 对象工厂
class ConnectionPooledObjectFactory implements PooledObjectFactory<Connection> {

    private final String url;
    private final String user;
    private final String password;
    private ObjectPool<Connection> pool;

    public ConnectionPooledObjectFactory(Properties properties) {
        this.url = properties.getProperty("url");
        this.user = properties.getProperty("user");
        this.password = properties.getProperty("password");
    }
    @Override
    public void activateObject(PooledObject<Connection> p) throws Exception {
        System.out.println("activateObject " + p.getObject());
    }

    @Override
    public void destroyObject(PooledObject<Connection> p) throws Exception {
        System.out.println("destroyObject " + p.getObject());
        Connection conn = p.getObject(); // jdk动态代理对象，有一个名为h的属性，类型为InvocationHandler
        Field field_h = conn.getClass().getSuperclass().getDeclaredField("h");
        boolean field_h_accessible = field_h.isAccessible();
        field_h.setAccessible(true);
        InvocationHandler obj_h = (InvocationHandler)field_h.get(conn);
        Field field_h_conn = obj_h.getClass().getDeclaredField("conn");
        boolean field_h_conn_accessible = field_h_conn.isAccessible();
        field_h_conn.setAccessible(true);
        Connection origin = (Connection)field_h_conn.get(obj_h);
        try {
            System.out.println("origin: " + origin);
            origin.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        field_h_conn.setAccessible(field_h_conn_accessible);
        field_h.setAccessible(field_h_accessible);
    }

    // 实际创建对象
    @Override
    public PooledObject<Connection> makeObject() throws Exception {
        Connection conn = DriverManager.getConnection(url, user, password);
        // 将对象包装为一个代理，拦截close方法，不实际关闭连接，而是归还到池中
        Connection proxy1 = JdkProxyUtil.getProxy(Connection.class,
                new ConnectionInvocationHandler(pool, conn));
        System.out.println("makeObject " + conn);
        return new DefaultPooledObject<>(proxy1);
    }

    @Override
    public void passivateObject(PooledObject<Connection> p) throws Exception {
        System.out.println("passivateObject " + p.getObject());
    }

    @Override
    public boolean validateObject(PooledObject<Connection> p) {
        System.out.println("validateObject " + p.getObject());

        try {
            return !p.getObject().isClosed();
        } catch (SQLException e) {
            return false;
        }
    }
}

class ConnectionInvocationHandler implements InvocationHandler {
    private final ObjectPool<Connection> pool;
    private final Connection conn;
    public ConnectionInvocationHandler(ObjectPool<Connection> pool, Connection conn) {
        this.pool = pool;
        this.conn = conn;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("close".equals(method.getName())) {
            pool.returnObject((Connection) proxy);
            return null;
        } else {
            return method.invoke(conn, args);
        }
    }
}